package org.dreamwork.drawing.framework;

import java.awt.*;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
import java.util.Vector;

/**
 * Created by IntelliJ IDEA.
 * User: user
 * Date: 2005-12-21
 * Time: 21:23:46
 */
public class Line implements java.io.Serializable {
    public static final int MODE_SOLID = 0;
    public static final int MODE_DASH = 1;
    public static final int E = 5;

    protected DrawingCanvas canvas;
    protected Figure[] figure = new Figure [2];
    protected Color color = Color.BLACK;
    protected Point p0, p1;
    private boolean showArrow = true;
    private boolean selected;

    private int mode = MODE_SOLID;

    private Vector<MouseListener> mouseListeners = new Vector<MouseListener> ();
    private Vector<MouseMotionListener> mouseMotionListeners = new Vector<MouseMotionListener> ();

    public Line () {}

    public Line (Figure... firgure) {
        if (firgure.length == 1) {
            this.figure [0] = firgure [0];
            this.figure [1] = firgure [0];
        } else if (firgure.length > 1) {
            this.figure [0] = firgure [0];
            this.figure [1] = firgure [1];
        }
    }

    public Figure getStartFigure () { return figure [0]; }
    public void setStartFigure (Figure figure) { this.figure [0] = figure; }
    public Figure getEndFigure () { return figure [1]; }
    public void setEndFigure (Figure figure) { this.figure [1] = figure; }

    public void paint (Graphics2D g) {
        g.setColor (color);
        p0 = figure [0].getCenter ();
        p1 = figure [1].getCenter ();
        if (p0.x == p1.x && p0.y == p1.y) { // 指向自己的线
            drawSelf (g);
        } else {
            if (p0.x == p1.x) { // 水平线
                drawVLine (g);
            } else if (p0.y == p1.y){ // 垂直线
                drawHLine (g);
            } else {
                drawLine (g);
            }
        }
    }

    /**
     * 画一根指向自己的线.
     * 从右边的中点画一根折线到上边中点
     * @param g 图形环境
     */
    private void drawSelf  (Graphics2D g) {
        store (g);
        Point start = new Point ();
        Point end   = new Point ();
        Point p = figure [0].getCenter ();
        Rectangle rec = figure [0].getRectangle ();
        start.x = rec.x + rec.width;
        end.x   = start.x + 20;
        start.y = p.y;
        end.y   = start.y;
        g.drawLine ( start.x, start.y, end.x, end.y );
        if (selected) g.fillRect (start.x, start.y, E, E);
        if (selected) g.fillRect (end.x - E / 2, end.y - E / 2, E, E);

        start = (Point) end.clone ();
        end.y = end.y - rec.height / 2 - 20;
        g.drawLine ( start.x, start.y, end.x, end.y );
        if (selected) g.fillRect (end.x - E / 2, end.y - E / 2, E, E);

        start = (Point) end.clone ();
        end.x = end.x - rec.width / 2 - 20;
        g.drawLine ( start.x, start.y, end.x, end.y );
        if (selected) g.fillRect (end.x - E / 2, end.y - E / 2, E, E);

        start = (Point) end.clone ();
        end.y = rec.y;
        g.drawLine ( start.x, start.y, end.x, end.y );
        if (selected) g.fillRect (end.x - E, end.y, E, E);
        restore (g);
        if (showArrow) drawArrow (g, start, end);
    }

    private void drawHLine (Graphics2D g) {
        store (g);
        Point p     = figure [0].getCenter ();
        Rectangle rec1 = figure [0].getRectangle ();
        Rectangle rec2 = figure [1].getRectangle ();
        Point start = new Point ();
        Point end   = new Point ();
        start.y = p.y;
        end.y   = p.y;
        if (rec1.x + rec1.width < rec2.x) {
            start.x = rec1.x + rec1.width;
            end.x   = rec2.x;
        }
        else if (rec2.x + rec2.width < rec1.x) {
            start.x = rec2.x + rec2.width;
            end.x   = rec1.x;
        }
        g.drawLine ( start.x, start.y, end.x, end.y );
        if (selected) {
            g.fillRect (start.x, start.y, E, E);
            g.fillRect (end.x - E, end.y - E, E, E);
        }
        restore (g);
        if (showArrow) {
            if (Math.abs (p1.x - start.x) > Math.abs (p1.x - end.x)) drawArrow (g, start, end);
            else drawArrow (g, end, start);
        }
    }

    private void drawVLine (Graphics2D g) {
        store (g);
        Point p = figure [0].getCenter ();
        Rectangle rec1 = figure [0].getRectangle ();
        Rectangle rec2 = figure [1].getRectangle ();
        Point start = new Point ();
        Point end   = new Point ();
        start.x = p.x;
        end.x   = p.x;
        if (rec1.y + rec1.height < rec2.y) {
            start.y = rec1.y + rec1.height;
            end.y   = rec2.y;
        } else if ( rec2.y + rec2.height < rec1.y ) {
            start.y = rec2.y + rec2.height;
            end.y   = rec1.y;
        }
        g.drawLine ( start.x, start.y, end.x, end.y );
        if (selected) {
            int minx = Math.min (start.x, end.x);
            int miny = Math.min (start.y, end.y);
            g.fillRect (start.x + minx == start.x ? 0 : -E, start.y + miny == start.y ? -E : 0, E, E);
            g.fillRect (end.x + minx == end.x ? - E : 0, end.y + miny == end.y ? 0 : - E, E, E);
        }
        restore (g);
        if (showArrow) {
            if (Math.abs (p1.y - start.y) > Math.abs (p1.y - end.y)) drawArrow (g, start, end);
            else drawArrow (g, end, start);
        }
    }

    private void drawLine (Graphics2D g) {
        Rectangle rec1 = figure [0].getRectangle ();
        Rectangle rec2 = figure [1].getRectangle ();
        store (g);
        Point[] ps;
        if (p0.x < p1.x && p0.y < p1.y) { // p0 在 p1 的左上方
            ps = calcute2 (rec1, rec2);
            g.drawLine ( ps [0].x, ps [0].y, ps [1].x, ps [1].y );
            restore (g);
            if (showArrow) drawArrow (g, ps [0], ps [1] );
        } else if (p0.x < p1.x && p0.y > p1.y) { // p0 在 p1 的左下方
            ps = calcute1 (rec1, rec2);
            g.drawLine ( ps [0].x, ps [0].y, ps [1].x, ps [1].y );
            restore (g);
            if (showArrow) drawArrow (g, ps [0], ps [1] );
        } else if (p0.x > p1.x && p0.y < p1.y) { //
            ps = calcute1 (rec2, rec1);
            g.drawLine ( ps [0].x, ps [0].y, ps [1].x, ps [1].y );
            restore (g);
            if (showArrow) drawArrow (g, ps [1], ps [0] );
        } else {
            ps = calcute2 (rec2, rec1);
            g.drawLine ( ps [0].x, ps [0].y, ps [1].x, ps [1].y );
            restore (g);
            if (showArrow) drawArrow (g, ps [1], ps [0] );
        }
        if (selected) {
            g.fillRect (ps [0].x, ps [0].y, E, E);
            g.fillRect (ps [1].x - E, ps [1].y - E, E, E);
        }
    }

    private Point[] calcute1 (Rectangle rec1, Rectangle rec2) {
        Point start = new Point ();
        Point end   = new Point ();
        double A = getA (p0, p1);
        double B = getB (p0, p1);
        // 直线和 figure [0] 的交点应该在上边或右边
        int x = (int) ((rec1.y - B) / A);
        if (x >= rec1.x && x <= rec1.x + rec1.width) {
            start.x = x;
            start.y = rec1.y;
        } else {
            int y = (int) (A * (rec1.x + rec1.width) + B);
            if (y >= rec1.y && y <= rec1.y + rec1.height) {
                start.x = rec1.x + rec1.width;
                start.y = y;
            }
        }
        x = (int) ((rec2.y + rec2.height - B) / A);
        if (x >= rec2.x && x <= rec2.x + rec2.width) {
            end.x = x;
            end.y = rec2.y + rec2.height;
        } else {
            int y = (int) (A * rec2.x + B);
            if (y >= rec2.y && y <= rec2.y + rec2.height) {
                end.x = rec2.x;
                end.y = y;
            }
        }

        return new Point[] {start, end};
    }

    private Point[] calcute2 (Rectangle rec1, Rectangle rec2) {
        double A = getA (p0, p1);
        double B = getB (p0, p1);
        Point start = new Point ();
        Point end = new Point ();
            int x = (int) ((rec1.y + rec1.height - B) / A);
            if (x >= rec1.x && x <= rec1.x + rec1.width) { // 在下边界又交点
                start.x = x;
                start.y = rec1.y + rec1.height;
            } else {
                int y = (int) (A * (rec1.x + rec1.width) + B);
                if (y >= rec1.y && y <= rec1.y + rec1.height) {
                    start.x = rec1.x + rec1.width;
                    start.y = y;
                }
            }
            x = (int) ((rec2.y - B) / A);
            if (x >= rec2.x && x <= rec2.x + rec2.width) {
                end.x = x;
                end.y = rec2.y;
            } else {
                int y = (int) (A * rec2.x + B);
                if (y >= rec2.y && y <= rec2.y + rec2.height) {
                    end.x = rec2.x;
                    end.y = y;
                }
            }

        return new Point[] {start, end};
    }

    // y = Ax + B
    private double getA (Point p0, Point p1) {
        return ((double) p0.y - p1.y) / ((double) p0.x - p1.x);
    }

    private double getB (Point p0, Point p1) {
        double A = getA (p0, p1);
        return (double) p0.y - A * p0.x;
    }

    static final int ANGLE = 30, ARROW_HEIGHT = 15;

    private void drawArrow (Graphics g, Point p0, Point p1) {
        int startAngle;
        if (p0.x == p1.x) { // 锤子线
            if (p0.y > p1.y) startAngle = -105; // 箭头朝上
            else startAngle = 75;
        } else if (p0.y == p1.y) { // 水平线
            if (p0.x > p1.x) startAngle = -15; // 箭头朝左
            else startAngle = 165;
        } else {
            double A = getA (p0, p1);
            if (p0.x > p1.x)
                startAngle = 360 - (int) (Math.atan (A) * 180 / Math.PI) - ANGLE / 2;
            else
                startAngle = 180 - (int) (Math.atan (A) * 180 / Math.PI) - ANGLE / 2;
        }
        Point pc = new Point (p1.x - ARROW_HEIGHT, p1.y - ARROW_HEIGHT);
        g.fillArc (pc.x, pc.y, 2 * ARROW_HEIGHT, 2 * ARROW_HEIGHT, startAngle, ANGLE);
    }

    public boolean isShowArrow () {
        return showArrow;
    }

    public void setShowArrow ( boolean showArrow ) {
        this.showArrow = showArrow;
    }

    public int getMode () {
        return mode;
    }

    public void setMode ( int mode ) {
        this.mode = mode;
    }

    BasicStroke old = null;

    private void store (Graphics2D g) {
        BasicStroke s = (BasicStroke) g.getStroke ();
        if (mode == MODE_DASH) {
            float width = s.getLineWidth ();
            float miterLimit = s.getMiterLimit ();
            int cap = s.getEndCap ();
            BasicStroke ns = new BasicStroke (width, cap, BasicStroke.JOIN_ROUND, miterLimit, new float[]{5f}, BasicStroke.CAP_ROUND );
            g.setStroke ( ns );
        }
        old = s;
    }

    private void restore (Graphics2D g) {
        g.setStroke ( old );
        old = null;
    }

    public boolean contains (Point p) {
        if (p0.x == p1.x) { // 垂直先
            if (p.x <= p0.x + E && p.x >= p0.x - E) {
                int min = Math.min (p0.y,  p1.y);
                int max = Math.max (p0.y,  p1.y);
                if (p.y <= max && p.y >= min) return true;
            }
        } else if (p0.y == p1.y) { // 水平线
            if (p.y <= p0.y + E && p.y >= p0.y - E) {
                int min = Math.min (p0.x, p1.x);
                int max = Math.max (p0.x, p1.x);
                if (p.x < max && p.x >= min) return true;
            }
        } else { // 普通直线
            double A = getA (p0, p1);
            double B = getB (p0, p1);
            int y = (int) (A * p.x + B);
            if (p.y <= y + E && p.y >= y - E) return true;
        }

        return false;
    }

    public void selecte () {
        this.selected = true;
    }

    public void unselect () {
        this.selected = false;
    }

    public boolean isSelected () {
        return selected;
    }

    public synchronized void addMouseListener ( MouseListener l) {
        mouseListeners.addElement ( l );
    }

    public synchronized void addMouseMotionListener ( MouseMotionListener l ) {
        mouseMotionListeners.addElement ( l );
    }

    public synchronized void removeMouseListener ( MouseListener l ) {
        mouseListeners.removeElement ( l );
    }

    public synchronized void removeMouseMotinoListener ( MouseMotionListener l ) {
        mouseMotionListeners.removeElement ( l );
    }

    public synchronized MouseListener[] getMouseListeners () {
        MouseListener [] mls = new MouseListener [mouseListeners.size ()];
        return mouseListeners.toArray ( mls );
    }

    public synchronized MouseMotionListener[] getMouseMotionListeners () {
        MouseMotionListener [] mls = new MouseMotionListener [mouseMotionListeners.size ()];
        return mouseMotionListeners.toArray ( mls );
    }

    public synchronized void fireMouseClick ( MouseEvent e ) {
        for (int i = 0; i < mouseListeners.size (); i ++) {
            mouseListeners.elementAt (i).mouseClicked ( e );
        }
    }

    public synchronized void fireMousePress ( MouseEvent e ) {
        for (int i = 0; i < mouseListeners.size (); i ++) {
            mouseListeners.elementAt ( i ).mousePressed ( e );
        }
    }

    public synchronized void fireMouseEnter ( MouseEvent e ) {
        for (int i = 0; i < mouseListeners.size (); i ++) {
            mouseListeners.elementAt ( i ).mouseEntered ( e );
        }
    }

    public synchronized void fireMouseRelease ( MouseEvent e ) {
        for (int i = 0; i < mouseListeners.size (); i ++) {
            mouseListeners.elementAt ( i ).mouseReleased ( e );
        }
    }

    public synchronized void fireMouseExit ( MouseEvent e ) {
        for (int i = 0; i < mouseListeners.size (); i ++) {
            mouseListeners.elementAt ( i ).mouseExited ( e );
        }
    }

    public synchronized void fireMouseDrag ( MouseEvent e ) {
        for (int i = 0; i < mouseMotionListeners.size (); i ++) {
            mouseMotionListeners.elementAt ( i ).mouseDragged ( e );
        }
    }

    public synchronized void fireMouseMove ( MouseEvent e ) {
        for (int i = 0; i < mouseMotionListeners.size (); i ++) {
            mouseMotionListeners.elementAt ( i ).mouseMoved ( e );
        }
    }
}